#
# Copyright (c) 2023 supercell
#
# SPDX-License-Identifier: BSD-3-Clause
#

module Luce
  # Matches backtick-enclosed inline code blocks
  class CodeSyntax < InlineSyntax
    # This pattern matches:
    #
    # * a string of backticks (not followed by any more), followed by
    # * a non-greedy string of anything, including newlines, ending
    #   with anything except a backtick, followed by
    # * a string of backticks the same length as the first, not
    #   followed by any more.
    #
    # This conforms to the delimiters of inline code, both in
    # Markdown.pl, and CommonMark.
    @pattern_ = %q{(`+(?!`))((?:.|\n)*?[^`])\1(?!`)}

    def initialize
      super(@pattern_)
    end

    def matches?(parser : InlineParser, start_pos : Int32? = nil) : Bool
      if parser.pos > 0 && parser.char_at(parser.pos - 1) == Charcode::BACKQUOTE
        # Not really a match! We can't just sneak past one backtick to
        # try the next character. An example of this situation would be
        #
        #    before ``` and `` after.
        #            ^--parser.pos
        return false
      end

      match = @pattern.match(parser.source, parser.pos)
      if match.nil? || match.begin != parser.pos
        return false
      end
      parser.write_text
      parser.consume(match[0].size) if on_match(parser, match)
      true
    end

    def on_match(parser : InlineParser, match : Regex::MatchData) : Bool
      marker_length = match[1].size
      content_length = match[0].size - marker_length * 2
      content_start = parser.pos + marker_length
      content_end = content_start + content_length

      code = parser.source[content_start...content_end]

      code = code[1...-1] if should_strip?(code)
      code = code.gsub("\n", " ")
      code = Luce.escape_html(code, escape_apos: false) if parser.encode_html?

      parser.add_node Element.text("code", code)

      true
    end

    private def should_strip?(code : String) : Bool
      # No stripping occurs if the code span contains only spaces:
      # https://spec.commonmark.org/0.30/#example-334.
      return false if code.strip.empty?

      # Only spaces, and not unicode whitespace in general, are stripped in this
      # way, see https://spec.commonmark.org/0.30/#example-333.
      starts_with_space = code.starts_with?(" ") || code.starts_with?("\n")
      ends_with_space = code.ends_with?(" ") || code.ends_with?("\n")

      # The stripping only happens if the space is on both sides of the string:
      # https://spec.commonmark.org/0.30/#example-332.
      return false if !starts_with_space || !ends_with_space

      true
    end
  end
end
